#region Usings

using System;
using System.Collections.Generic;
using System.Linq;
using SmartLib.Core;
using SmartLib.Properties;

#endregion

namespace SmartLib.Common
{
	/// <summary>
	/// Service Locator
	/// </summary>
	public class ServiceLocator : SingletonBase<ServiceLocator>, IServiceLocator
	{
		private readonly IDictionary<Type, IList<TypeHost>> _mappings = new Dictionary<Type, IList<TypeHost>>();

		#region IServiceLocator Members

		/// <summary>
		/// Registers the types.
		/// </summary>
		/// <param name = "fromType">From type.</param>
		/// <param name = "toType">To type.</param>
		/// <param name = "singleton">if set to <c>true</c> [singleton].</param>
		public void Register(Type fromType, Type toType, bool singleton)
		{
			if (fromType == null)
				throw new ArgumentNullException("fromType");

			if (toType == null)
				throw new ArgumentNullException("toType");

			if (IsRegistered(fromType, toType))
				throw new OperationCanceledException(string.Format(Resources.ErrMsg_TypeIsRegisteredWith, fromType, toType));

			var lst = _mappings.ContainsKey(fromType)
			          	? _mappings[fromType]
			          	: new List<TypeHost>();

			lst.Add(new TypeHost(toType, singleton));
		}

		/// <summary>
		/// Creates the instance.
		/// </summary>
		/// <typeparam name = "T"></typeparam>
		/// <param name = "fromType">From type.</param>
		/// <returns>The instance</returns>
		public T CreateInstance<T>(Type fromType)
		{
			var ctors = fromType.GetConstructors();
			if (ctors.Length < 1)
				throw new OperationCanceledException(string.Format(Resources.ErrMsg_TypeHasNoCtor, fromType));

			foreach (var ctor in ctors)
			{
				var parameters = ctor.GetParameters();
				var ary = new object[parameters.Length];

				for (var i = 0; i < parameters.Length; i++)
				{
					if (_mappings.ContainsKey(fromType))
						ary[i] = Resolve(parameters[i].ParameterType);
					else
						ary[i] = Activator.CreateInstance(parameters[i].ParameterType);
				}

				var instance = ctor.Invoke(ary);
				return (T) instance;
			}

			return default(T);
		}

		/// <summary>
		/// 	Resolves the instance by passed type.
		/// </summary>
		/// <typeparam name = "T">Cast to desired type</typeparam>
		/// <returns>The instance</returns>
		public T Resolve<T>()
		{
			var instance = Resolve(typeof (T));
			return (T) instance;
		}

		public T Resolve<T>(string name)
		{
			throw new NotImplementedException();
		}

		public T Resolve<T>(Type fromType)
		{
			throw new NotImplementedException();
		}

		public T Resolve<T>(Type fromType, string name)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// 	Determines whether the specified type is registered.
		/// </summary>
		/// <param name = "fromType">From type.</param>
		/// <param name = "toType">To type.</param>
		/// <returns>
		/// 	<c>true</c> if the specified type is registered; otherwise, <c>false</c>.
		/// </returns>
		public bool IsRegistered(Type fromType, Type toType)
		{
			if (!_mappings.ContainsKey(fromType)) return false;
			var count = _mappings[fromType].Count(item => item.Type == toType);
			return count > 0;
		}

		/// <summary>
		/// 	Determines whether [is registered as singleton] [the specified type].
		/// </summary>
		/// <param name = "fromType">From type.</param>
		/// <param name = "toType">To type.</param>
		/// <returns>
		/// 	<c>true</c> if [is registered as singleton] [the specified type]; otherwise, <c>false</c>.
		/// </returns>
		public bool IsRegisteredAsSingleton(Type fromType, Type toType)
		{
			if (!_mappings.ContainsKey(fromType)) return false;
			var count = _mappings[fromType].Count(item => item.Type == toType && item.Singleton);
			return count > 0;
		}

		#endregion

		/// <summary>
		/// 	Releases unmanaged resources and performs other cleanup operations before the
		/// 	<see cref = "ServiceLocator" /> is reclaimed by garbage collection.
		/// </summary>
		~ServiceLocator()
		{
			foreach (var pair in _mappings)
			{
				pair.Value.Clear();
			}
			_mappings.Clear();
		}

		/// <summary>
		/// 	Resolves the specified from type.
		/// </summary>
		/// <param name = "fromType">From type.</param>
		/// <returns></returns>
		private object Resolve(Type fromType)
		{
			object instance;
			if (!_mappings.ContainsKey(fromType))
			{
				instance = CreateInstance<object>(typeof (Type));
				return instance;
			}

			var host = _mappings[fromType].FirstOrDefault();
			if (host == null) return null;

			if (host.Singleton)
			{
				return host.Instance ?? 
					(host.Instance = CreateInstance<object>(typeof (Type)));
			}

			instance = CreateInstance<object>(typeof (Type));
			return instance;
		}
	}
}